var DragAndDropManager = require('geoDragAndDropManager');
var Draggable = require('geoDraggable');
var Utils = require('geoUtils');
/**
 * 接收拖放事件的组件
 */
cc.Class({
    extends: cc.Component,

    properties: {
        priority: {
            default: 0,
            type: cc.Integer,
            tooltip: '接收拖放优先级，越小优先级越高',
        },
        dropArea: {
            default: null,
            type: cc.Node,
            tooltip: '接收拖放的区域'
        },
        stopHere: {
            default: true,
            tooltip: '拖放事件传递到这里就终止'
        },
        useBounds: {
            default: true,
            tooltip: '拖放时，使用轮廓还是使用拖放点'
        },
        ignoreChildrenBounds: {
            default: true,
            tooltip: '忽略子节点尺寸造成的拖放区域扩增'
        },
        mustInArea: {
            default: true,
            tooltip: '拖放是否必须在拖放区才奏效'
        },
        dropEvents: {
            default: [],
            type: [cc.Component.EventHandler],
            tooltip: '有物品拖放来时，触发'
        },
        enterEvents: {
            default: [],
            type: [cc.Component.EventHandler],
            tooltip: '当拖放物进入拖放区时调用。'
        },
        hoverEvents: {
            default: [],
            type: [cc.Component.EventHandler],
            tooltip: '当托放物悬在拖放区上时调用。'
        },
        exitEvents: {
            default: [],
            type: [cc.Component.EventHandler],
            tooltip: '当拖放物移出拖放区时调用'
        },

        // dropOnLoad:true,
    },

    // LIFE-CYCLE CALLBACKS:
    start() {
        if (this.dropArea === null)
            this.dropArea = this.node;

        // if (this.dropOnLoad === true)
        //     this.enableDrop();

    },
    enableDrop() {
        this.disableDrop();
        //
        DragAndDropManager.Singleton.
            addDragAndDropListener(DragAndDropManager.EventType.DRAG_END, this._onDropFromManagerCallback, this.priority);
    },
    disableDrop() {
        this._onDropFromManagerCallback = this._onDropFromManagerCallback || this._onDropFromManager.bind(this);
        DragAndDropManager.Singleton.
            removeDragAndDropListener(DragAndDropManager.EventType.DRAG_END, this._onDropFromManagerCallback, this.priority);
    },
    // 针对CollisionManager类型的碰撞
    onCollisionEnter(other, self) {
        this._handleCollision(other, this.enterEvents,
            DragAndDropManager.EventType.DROP_ENTER);
    },
    // 针对CollisionManager类型的碰撞
    onCollisionStay(other, self) {
        this._handleCollision(other, this.hoverEvents,
            DragAndDropManager.EventType.DROP_HOVER);
    },
    // 针对CollisionManager类型的碰撞
    onCollisionExit(other, self) {
        this._handleCollision(other, this.exitEvents,
            DragAndDropManager.EventType.DROP_EXIT);
    },
    // 针对PhysicsManager类型的碰撞
    // 只在两个碰撞体开始接触时被调用一次
    onBeginContact: function (contact, selfCollider, otherCollider) {
        this._handleCollision(otherCollider, this.enterEvents,
            DragAndDropManager.EventType.DROP_ENTER);
    },
    // 针对PhysicsManager类型的碰撞
    // 只在两个碰撞体结束接触时被调用一次
    onEndContact: function (contact, selfCollider, otherCollider) {
        this._handleCollision(otherCollider, this.exitEvents,
            DragAndDropManager.EventType.DROP_EXIT);
    },

    // 针对PhysicsManager类型的碰撞
    // 每次将要处理碰撞体接触逻辑时被调用
    // onPreSolve: function (contact, selfCollider, otherCollider) {
    // },

    // 针对PhysicsManager类型的碰撞
    // 每次处理完碰撞体接触逻辑时被调用
    onPostSolve: function (contact, selfCollider, otherCollider) {
        this._handleCollision(otherCollider, this.hoverEvents,
            DragAndDropManager.EventType.DROP_HOVER);
    },
    // 处理碰撞的通用方法
    _handleCollision(other, eventHandlers, eventType) {
        this._currentCollisionTarget = null;
        //
        var area = this.dropArea;
        var draggable = Utils.node.getComponentInParents(other.node, Draggable);
        if (draggable == null || draggable.useDrop == false)
            return;
        //
        var item = draggable.getDragTarget();
        // 若不符合条件则放弃后面的处理。
        if (this._isTargetDropTagValid(item) == false)
            return;
        //
        this._currentCollisionTarget = item;
        // 通过以上检测说明拖放成功，准备发送拖放事件。
        var e = new cc.Event.EventCustom(eventType);
        //
        cc.Component.EventHandler.emitEvents(eventHandlers, e,
            item, area);

        // 推出碰撞时要清空缓存
        if (eventType == DragAndDropManager.EventType.DROP_EXIT) {
            // 下一帧时清空缓存的碰撞目标
            this.scheduleOnce((() => {
                this._currentCollisionTarget = null;
            }).bind(this));
        }
    },
    _onDropFromManager(dropEvent) {
        // 如果被禁用，就不响应
        if (this.enabledInHierarchy === false)
            return;
        //
        var target = dropEvent.target;
        var targetArea = dropEvent.area;
        // 判定标签
        // var dropTag = Utils.text.getQueryString(this.dropArea.name, 'drop');
        // if (dropTag != null) {
        //     var targetTag = Utils.text.getQueryString(target.name, 'drop');
        //     if (targetTag != null) {
        //         // 进行标签匹配
        //         var matchResult = Utils.array.countMatch([dropTag.split(' '), targetTag.split(' ')], [2]);
        //         // 匹配不成功，则返回，终止之后的拖放操作
        //         if (Object.getOwnPropertyNames(matchResult).length === 0)
        //             return;
        //     } else {
        //         return;
        //     }
        // }
        if (this._isTargetDropTagValid(target) == false)
            return;
        //
        if (this.mustInArea === true) {
            // // var dropRect = this.dropArea.getBoundingBoxToWorld();
            // var dropRect = this._getBoundingBox(this.dropArea, this.ignoreChildrenBounds);
            // if (this.useBounds === true) {
            //     // 检测是否在拖放区
            //     var r2 = targetArea.getBoundingBoxToWorld();
            //     if (dropRect.intersects(r2) === false)
            //         return;
            // } else {
            //     var location = dropEvent.location;
            //     if (dropRect.contains(location) === false)
            //         return;
            // }
            if (this.useBounds === true) {
                if (this._isTargetAreaValid(target, targetArea) === false)
                    return;
            } else {
                if (this._isDragLocationValid(dropEvent.location) === false)
                    return;
            }
        }
        // 通过以上检测说明拖放成功，准备发送拖放事件。
        var e = new cc.Event.EventCustom(DragAndDropManager.EventType.DROP_END);
        //
        cc.Component.EventHandler.emitEvents(this.dropEvents, e, target, this.dropArea, dropEvent.touch);
        //
        dropEvent.stop = this.stopHere;
    },
    onEnable() {
        this.enableDrop();
    },
    onDisable() {
        this.disableDrop();
    },
    onDestroy() {
        this.disableDrop();
    },
    /**
     * 验证拖放物携带的拖放标签是否有效，以进行进一步拖放处理。
     * @param {cc.Node} target 拖放物。 
     */
    _isTargetDropTagValid(target) {
        // 判定标签
        var dropTag = Utils.text.getQueryString(this.dropArea.name, 'drop');
        if (dropTag != null) {
            var targetTag = Utils.text.getQueryString(target.name, 'drop');
            if (targetTag != null) {
                // 进行标签匹配
                var matchResult = Utils.array.countMatch([dropTag.split(' '), targetTag.split(' ')], [2]);
                // 匹配不成功，则返回，终止之后的拖放操作
                if (Object.getOwnPropertyNames(matchResult).length === 0)
                    return false;
            } else {
                return false;
            }
        }
        //
        return true;
    },
    /**
     * 检测目标区域是否构成拖放
     */
    _isTargetAreaValid(target, targetArea) {
        // 获取碰撞体
        // var collidersInArea = this.dropArea.getComponents(cc.Collider);
        // var collidersInItem = targetArea.getComponents(cc.Collider);
        // TODO: 当前仅使用简单算法
        if (this._isCollisionInUse(this.dropArea) &&
            this._isCollisionInUse(targetArea)) {
            if (this._currentCollisionTarget === target)
                return true;
            else
                return false;
        }
        // 获取拖放区的包围盒
        var dropRect = this._getBoundingBox(this.dropArea, this.ignoreChildrenBounds);
        // 获取目标的包围盒
        var r2 = targetArea.getBoundingBoxToWorld();
        // 测试两个包围盒是否相交
        return dropRect.intersects(r2);
    },
    /**
     * 判断此节点的碰撞是否处于启用状态
     * @param {cc.Node} node 
     * @returns true，如果节点有处于启用状态的碰撞体，并且若存在rigidbody且enableContactListener。
     */
    _isCollisionInUse(node){
        // 若启用物理引擎，查看是否rigidbody启用碰撞监听
        if (cc.director.getPhysicsManager().enabled == true) {
            var rigid = node.getComponent(cc.RigidBody);
            if (rigid && rigid.enabledContactListener == false)
                return false;
        } else if (cc.director.getCollisionManager().enabled == false) {
            // 若物理引擎和碰撞引擎都没有开启，即放弃精细检测
            return false;
        }
        // 查看碰撞体是否有开启的
        return this._isCollidersInUse(node.getComponents(cc.Collider));
    },
    /**
     * 判定给定的碰撞体是否工作中
     * @param {[cc.Collider]} colliders
     * @returns 只要给定的colliders中有一个处于工作状态，就返回true，否则返回false。
     */
    _isCollidersInUse(colliders) {
        //
        var collider;
        for (var i = 0; i < colliders.length; ++i) {
            collider = colliders[i];
            if (collider.enabledInHierarchy == true)
                return true;
        }
        //
        return false;
    },
    /**
     * 检测拖拽位置是否构成拖放
     */
    _isDragLocationValid(location) {
        // 获取拖放区的包围盒
        var dropRect = this._getBoundingBox(this.dropArea, this.ignoreChildrenBounds);
        // 测试是否拖拽点在拖放区内
        return dropRect.contains(location);
    },
    _getBoundingBox(node, ignoreChildren) {
        if (ignoreChildren == true) {
            var b = node.getBoundingBox();
            if (node.parent != null) {
                // 
                let worldMat = cc.vmath.mat4.create();
                node.parent.getWorldMatrix(worldMat);
                b.transformMat4(b, worldMat);
            }
            return b;
        } else {
            return node.getBoundingBoxToWorld();
        }
    },
    clone(com) {
        com.priority = this.priority;
        com.dropArea = this.dropArea;
        com.stopHere = this.stopHere;
        com.useBounds = this.useBounds;
        com.ignoreChildrenBounds = this.ignoreChildrenBounds;
        com.mustInArea = this.mustInArea;
        com.dropEvents = this.dropEvents;
    },

    // update (dt) {},
});
